'''
Created on 28 sept. 2010
@author: Marc CARRE
@contact: carre.marc@gmail.com
@license: GNU GPLv3
@version: 1.0
'''

from math import *

class SpacePartition:

    def __init__(self, label, params, limits, space):
        self.checkInput(label, params, limits, space)
        self.label = label
        self.params = params
        self.limits = limits
        self.space = space
        self.safe_dict = self.createSafeDict()
        
    def checkInput(self, label, params, limits, space):
        self.checkInputType(label, params, limits, space)
        self.checkInputRelevance(params, limits, space)
        
        
    def checkInputType(self, label, params, limits, space):
        if not type(label) is str:
            raise ValueError("The label needs to be a string object, e.g. : 'A' or ""B"".")
        
        if type(params) != list:
            raise ValueError("Parameters must be an array of string, e.g. : ['x',""y""].")
        
        for param in params:
            if not type(param) is str:
                raise ValueError("Parameters' values must be string objects, e.g. : 'x' or ""y"".")
            
        if type(limits) != list:
            raise ValueError("Limits must be an array of array of numerics, e.g. : [ [-2, 2], [0.1, 4] ].")
        
        for limit in limits:
            if type(limit) != list:
                raise ValueError("Each limit object must be a pair of numerics [min, max], e.g. : [-2, 2], or [0.1, 4].")
            for elem in limit:
                if (type(elem) != int) and (type(elem) != float):
                    raise ValueError("Each limit object must be a pair of numerics [min, max], e.g. : [-2, 2], or [0.1, 4].")
        
        
    def checkInputRelevance(self, params, limits, space):
        if len(limits) != len(params):
            raise ValueError("Incorrect number of limits: you should have one limit for each parameter, e.g. : ['x','y'] -> [ [-2, 2], [0.1, 4] ].")
        
        for limit in limits:
            if len(limit) != 2:
                raise ValueError("Incorrect limit: you should have two numerics, one minimum, one maximum, for each limit object, e.g. : [-2, 2].")
            if limit[0] >= limit[1]:
                raise ValueError("Incorrect limit: the minimum should be strictly less than the maximum, e.g. : [-2, 2].")
        
        for param in params:
            if space.find(param) == -1:
                raise Warning("The parameter " + param + " is not used in the definition of the space '" + space + "'.")
        
        
    def isInSpace(self, point):
        if not self.isValidPoint(point):
            return False
        
        values = {}
        for i in range(len(self.params)):
            values[self.params[i]] = point[i]
        values["__builtins__"] = None
        return eval(self.space, values, self.safe_dict)
    
    
    def isValidPoint(self, point):
        if (len(point) != len(self.params)):
            raise ValueError("Value(s) missing for the point's coordinates.")
        
        for i in range(len(self.params)):
            if point[i] < self.limits[i][0]:
                raise ValueError("One coordinate's value is less than the minimum.")
            if point[i] > self.limits[i][1]:
                raise ValueError("One coordinate's value is more than the maximum.")
            
        return True
        
    def createSafeDict(self):
        safe_functions = ['math' , 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'cosh', 'degrees', 'e', 'exp', 'fabs', 'floor', 'fmod', 'frexp', 'hypot', 'ldexp', 'log', 'log10', 'modf', 'pi', 'pow', 'radians', 'sin', 'sinh', 'sqrt', 'tan', 'tanh']
        safe_dict = dict([ (k, locals().get(k, None)) for k in safe_functions ])
        safe_dict['abs'] = abs
        safe_dict['acos'] = acos
        safe_dict['acosh'] = acosh
        safe_dict['asin'] = asin
        safe_dict['asinh'] = asinh
        safe_dict['atan'] = atan
        safe_dict['atan2'] = atan2
        safe_dict['atanh'] = atanh
        safe_dict['ceil'] = ceil
        safe_dict['cos'] = cos
        safe_dict['cosh'] = cosh
        safe_dict['e'] = e
        safe_dict['exp'] = exp
        safe_dict['factorial'] = factorial
        safe_dict['floor'] = floor
        safe_dict['log'] = log
        safe_dict['log10'] = log10
        safe_dict['pi'] = pi
        safe_dict['pow'] = pow
        safe_dict['sin'] = sin
        safe_dict['sinh'] = sinh
        safe_dict['sqrt'] = sqrt
        safe_dict['tan'] = tan
        safe_dict['tanh'] = tanh
        safe_dict['trunc'] = trunc
        return safe_dict